package eol.vm;

import java.util.List;

import eol.vm.Machine.ExecutionError;


/** Values of the machines.
* <p>Each value is of one of five types. Type checking is done at run-time, within instructions.</p>
* <ul><li>The type of Nil
*     <li>Strings
*     <li>Numbers (Double precision)
*     <li>Cons cells, containing pointers to two other values.
*     <li>Closures (i.e. function values) consisting of a function block
*         and an environment.
* </ul> */
abstract class Value {
	static enum Type { DOUBLE, NIL, CONS, STRING, CLOSURE }
	
    static class DoubleValue extends Value {
	    Type getType() { return Type.DOUBLE ; }
	    double val ;
	    DoubleValue( double x ) { val = x ; }
	    double asDouble() { return val ; }
	    boolean isEqualTo( Value that ) {
	        return that.getType() == Type.DOUBLE
	                && ((DoubleValue)that).val == val ; }
	    public String toString() { return Double.toString( val ) ;  }
	}
	static class NilValue extends Value {
		private NilValue() {}
	    Type getType() { return Type.NIL ; }
	    boolean isEqualTo( Value that ) {
	        return that.getType() == Type.NIL ; }
	    public String toString() { return "[]" ; }
		static NilValue theNil = new NilValue() ;
	}
	static class StringValue extends Value {
	    Type getType() { return Type.STRING ; }
	    String val ;
	    StringValue( String x ) { val = x ; }
	    StringValue asStringValue() { return this ; }
	    boolean isEqualTo( Value that ) {
	        return that.getType() == Type.STRING 
	                && ((StringValue)that).val.equals( val ) ;
	    }
	    public String toString() { return "\"" + val + "\"" ; }
	}
	static class ConsValue extends Value {
	    Type getType() { return Type.CONS ; }
	    Value head ;
	    Value tail ;
	    ConsValue( Value hd, Value tl ) { head = hd ; tail = tl ; }
	    ConsValue asConsValue() { return this ; }
	    boolean isEqualTo( Value that ) {
	        return this == that ;
	    }
	    public String toString() {
	        if( isList() ) {
	            return "[" + restOfToString() ;
	        } else {
	            return "(" + head + " : " + tail + ")" ;
	        }
	    }
	    
	    private boolean isList() {
	        if( tail.isEqualTo(NilValue.theNil) )
	            return true ;
	        else if( tail.getType() == Type.CONS ){
	            ConsValue tl = (ConsValue) tail ;
	            return tl.isList() ; }
	        else 
	            return false ;
	    }
	    
	    private String restOfToString() {
	        if( tail.isEqualTo(NilValue.theNil) )
	            return head + "]" ;
	        else {
	            ConsValue tl = (ConsValue) tail ;
	            return head + ", " + tl.restOfToString() ; }
	    }
	}
	static class Closure extends Value {
			final Environment env ;
			final int segNum ; 
			final List<StringValue> params ;
			Closure( List<StringValue> params, Environment env, int segNum) {
				this.params = params ; this.env = env ; this.segNum = segNum ;
			}
	    Type getType() {return Type.CLOSURE ; }
	    Closure asClosure() { return this ; }
	    boolean isEqualTo( Value that ) { return this==that ; }
	    public String toString() { return "closure" ; }
	}
	abstract Type getType() ;
    double asDouble() throws ExecutionError { throw new ExecutionError("Operand is not double"); }
    StringValue asStringValue() throws ExecutionError { throw new ExecutionError("Operand is not string"); }
    ConsValue asConsValue() throws ExecutionError { throw new ExecutionError("Operand is not cons"); }
    Closure asClosure() throws ExecutionError { throw new ExecutionError("Operand is not cons"); }
    abstract boolean isEqualTo( Value that ) ;
}